#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <cctype>
#include <memory>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// 1.头文件引用 如果是.h和.c分开写 Makefile里要修改
// 2.打包成静态库/动态库使用
// 3.头文件 路径引用 .hpp
#include "singleThreadPool/Log.hpp"
#include "singleThreadPool/singleThreadPool.hpp"
#include "singleThreadPool/Task.hpp"

// 当前我们的服务器是一旦有客户端申请服务 服务端连接上后 只要客户端不退出 服务端就一直进行服务如回显他的消息
// 引进线程池处理任务后 对于这种长连接的服务 如果同时有超过g_threadNum个客户端申请服务
// 线程池里的线程就不够用了 客户端发来的服务请求即任务就会先放到队列里 直到有客户端服务结束退出
// 知识：线程数量要有上限 否则如果同时有大量客户申请服务 此时OS中的线程数量就会剧增 效率降低是一方面
// OS为了为新的客户提供服务 可能会停止先前的进程 这对OS是不好的
// 解决：
// 1. 参考OS设计的进程/线程调度问题 给一个客户服务长达一定时间 就停止对他服务 转向服务别人 一段时间后再来对他服务
// 2. 如果就是想要长期进行服务 这个问题后续再讲解


static void echoService(int serviceSocket, const std::string &clientip, const uint16_t &clientport)
{
    char buf[1024];
    while (true)
    {
        // 从服务套接字读取客户端发来的信息 将该信息显示在服务端 eg：客户端发来了xxx
        ssize_t s = read(serviceSocket, buf, sizeof(buf) - 1);
        if (s > 0)
        {
            buf[s] = 0; // 将发过来的数据当做字符串
            std::cout << clientip << ":" << clientport << " send# " << buf << std::endl;
        }
        else if (s == 0) // 对方停止写入
        {
            logMsg(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMsg(ERROR, "read::%d:%s", errno, strerror(errno));
            break;
        }

        // 把客户端发的信息再传回去
        write(serviceSocket, buf, strlen(buf));
    }
    close(serviceSocket);
}

static void Task_echo(int serviceSocket, const std::string &clientip,
                      const uint16_t &clientport, const std::string &threadName)
{
    char buf[1024];
    while (true)
    {
        // 在服务端显示客户端发送的信息
        ssize_t s = read(serviceSocket, buf, sizeof(buf) - 1);
        if (s > 0)
        {
            buf[s] = 0; // 将发过来的数据当做字符串
            std::cout << threadName << "|" << clientip << ":" << clientport << " client send# " << buf << std::endl;
        }
        else if (s == 0) // 对方停止写入或关闭连接
        {
            logMsg(NORMAL, "%s:%d client shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMsg(ERROR, "read::%d:%s", errno, strerror(errno));
            break;
        }

        // 把客户端发的信息再传回去
        write(serviceSocket, buf, strlen(buf));
    }
    close(serviceSocket);
}

static void Task_toggle(int serviceSocket, const std::string &clientip,
                        const uint16_t &clientport, const std::string &thread_name)
{
    char buf[1024];
    ssize_t s = read(serviceSocket, buf, sizeof(buf) - 1);
    if (s > 0)
    {
        buf[s] = 0; // 发过来的数据当做字符串
        std::cout << thread_name << "|" << clientip << ":" << clientport << " client send# " << buf << std::endl;
        std::string msg;
        char *start = buf;
        while (*start)
        {
            char character;
            if (islower(*start))
                character = toupper(*start);
            else
                character = *start;
            msg.push_back(character);
            start++;
        }

        write(serviceSocket, msg.c_str(), msg.size());
    }
    else if (s == 0) // 对方停止写入或关闭连接
    {
        logMsg(NORMAL, "%s:%d client shutdown, me too!", clientip.c_str(), clientport);
    }
    else
    {
        logMsg(ERROR, "read::%d:%s", errno, strerror(errno));
    }

    close(serviceSocket);
}

static void Task_Dictionary(int serviceSocket, const std::string &clientip,
                            const uint16_t &clientport, const std::string &thread_name)
{
    static std::unordered_map<std::string, std::string> dictionary = {
        {"apple", "苹果"},
        {"banana", "香蕉"},
        {"hard", "好难啊"}};

    char EnglishBuf[1024];
    ssize_t readBytes = read(serviceSocket, EnglishBuf, sizeof(EnglishBuf) - 1);
    if (readBytes > 0)
    {
        EnglishBuf[readBytes] = 0; // 将发过来的数据当做字符串
        std::cout << thread_name << "|" << clientip << ":" << clientport << "|"
                  << " client send# " << EnglishBuf << std::endl;

        std::string msg;
        auto iter = dictionary.find(EnglishBuf);
        if (iter == dictionary.end())
            msg = "unknown";
        else
            msg = iter->second;

        write(serviceSocket, msg.c_str(), msg.size());
    }
    else if (readBytes == 0) // 对方停止写入或关闭连接
    {
        logMsg(NORMAL, "%s:%d client shutdown!", clientip.c_str(), clientport);
    }
    else
    {
        logMsg(ERROR, "read::%d:%s", errno, strerror(errno));
    }

    close(serviceSocket);
}

/*// version 3多线程
class ThreadInfo
{
public:
    int _socketFd;
    std::string _ip;
    uint16_t _port;
};
*/

class TcpServer
{
private:
    const static int g_backlog = 20;

    /*// version 3多线程
    //类内成员函数做回调函数得是静态函数 -- this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); // 自动释放资源

        ThreadInfo *threadinfo = static_cast<ThreadInfo *>(args);
        echoService(threadinfo->_socketFd, threadinfo->_ip, threadinfo->_port);

        delete threadinfo;
        return nullptr;
    }
    */
private:
    uint16_t _port;
    std::string _ip;
    int _listenSocket;

    std::unique_ptr<singleThreadPool<Task>> _ptrToThreadPool;

public:
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _listenSocket(-1),
          _port(port),
          _ip(ip),
          _ptrToThreadPool(singleThreadPool<Task>::getThreadPoolInstance())
    {
    }

    void initServer()
    {
        // 1. 创建socket -- 进程+文件
        _listenSocket = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSocket < 0)
        {
            logMsg(FATAL, "socket::%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMsg(NORMAL, "show listenSocket: %d", _listenSocket); // fd = 3

        // 2. bind -- 文件+网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);

        local.sin_family = AF_INET;
        // inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);
        // inet_aton(_ip.c_str(), &local.sin_addr);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        local.sin_port = htons(_port);

        if (bind(_listenSocket, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMsg(FATAL, "bind::%d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. TCP面向连接 当正式通信时 要先建立连接
        if (listen(_listenSocket, g_backlog) < 0)
        {
            logMsg(FATAL, "listen::%d:%s", errno, strerror(errno));
            exit(4);
        }

        logMsg(NORMAL, "init server success");
    }

    void start()
    {
        // Version 2.0 主动忽略SIGCHLD信号 子进程退出时自动释放自己的资源
        // signal(SIGCHLD, SIG_IGN);

        _ptrToThreadPool->run();
        while (true)
        {
            // sleep(1);

            // 4. 接收连接accept
            struct sockaddr_in src_sockAddr;
            socklen_t len = sizeof(src_sockAddr);
            int serviceSocket = accept(_listenSocket, (struct sockaddr *)&src_sockAddr, &len);
            if (serviceSocket < 0)
            {
                logMsg(ERROR, "accept::%d:%s", errno, strerror(errno));
                continue;
            }

            // 接收连接成功
            uint16_t client_port = ntohs(src_sockAddr.sin_port);
            std::string client_ip = inet_ntoa(src_sockAddr.sin_addr);
            logMsg(NORMAL, "link success, serviceSocket: %d | %s : %d |\n",
                   serviceSocket, client_ip.c_str(), client_port);

            /* // Version 1单进程回显服务器
            // 一次处理一个客户端 处理完一个再处理下一个
            // 多个客户端给服务端发消息 服务端只能接收到第一个客户端的信息
            // 因为一旦第一个客户端连接上服务端后 服务端就调用echoService()
            // 该函数是一个死循环 他在不断地读取回显客户端发送的数据 无法再与新的客户端建立连接 除非有客户端退出
            echoService(serviceSocket, client_ip, client_port);
            */

            /*// Version 2.0多进程 信号处理子进程问题
            // 创建子进程 让子进程给新的连接提供服务 子进程能打开父进程曾经打开的文件
            pid_t id = fork();
            assert(id != -1);
            if (id == 0)
            {
                // 子进程是来进行提供服务的 不需要知道listenSocket
                // 关闭不需要的套接字 防止非法访问
                close(_listenSocket);

                echoService(serviceSocket, client_ip, client_port);
                exit(0);
            }

            // 父进程: 每一次accept一个客户端 都会创建一个serviceSocket
            // 如果在该层循环即将结束时 不关闭该层的serviceSocket fd资源会越来越少
            // 此处关闭serviceSocket 对父进程无影响 因为父进程只需要监听套接字 不需要提供服务的套接字
            // fd是一种有限资源【数组下标】有的内核是32/64 云服务器一般是5-10w
            // 服务器死循环 任何一种导致资源浪费的情况 都是服务器编程要考虑的
            close(serviceSocket); // 父进程关闭serviceSocket不会影响子进程

            // waitpid();
            // 多进程版初衷是让子进程为新客户端服务 此时父进程又来等待子进程的退出
            // 如果不等待子进程先于父进程退出会进入僵尸状态 如果等待又违反了多进程的初衷
            // ==>1.非阻塞式等待 但是较麻烦 OS会记录下子进程的pid 然后不断轮询 2.SIGCHILD
            */

            /*// version2.1多进程 创建孤儿进程
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程
                close(_listenSocket);
                if (fork() > 0)
                {
                    // 此处是子进程
                    close(serviceSocket); // 关闭服务套接字
                    exit(0); // 子进程退出
                }
                   
                // 孙子进程：孤儿进程退出时 OS自动回收
                // 子进程已退出 孙进程的父进程即["子进程"]先于自己退出 孙进程成为孤儿进程
                echoService(serviceSocket, client_ip, client_port);
                exit(0);
            }
            // 父进程
            // waitpid(id, nullptr, 0); // 子进程诞生即退出 无需阻塞等待
            close(serviceSocket);
            */

            /*// version 3多线程
            // 在栈上创建ThreadInfo对象 把该对象的地址传给pthread_create
            // threadRoutine去访问该对象 接着主线程就会进行下一次循环
            // 该对象生命周期结束 但是threadRoutine可能还未结束 -- 悬空指针
            ThreadInfo *threadinfo = new ThreadInfo();
            threadinfo->_socketFd = serviceSocket;
            threadinfo->_ip = client_ip;
            threadinfo->_port = client_port;
            pthread_t tid;

            // 多线程与主线程共享fd 当本次通信结束即对应的客户端退出 再关闭serviceSocket
            pthread_create(&tid, nullptr, threadRoutine, threadinfo);
            */

            // verison 4线程池
            // 此处为测试：默认知道客户端请求的是什么任务
            // Task taskEcho(serviceSocket, client_ip, client_port, Task_echo);
            // Task taskToggle(serviceSocket, client_ip, client_port, Task_toggle);
            Task taskDictionary(serviceSocket, client_ip, client_port, Task_Dictionary);

            _ptrToThreadPool->pushTask(taskDictionary);
        }
    }

    ~TcpServer()
    {
    }
};